using System;
using System.Collections.Generic;
using System.Text;
using SongsDB;
using SharpCast;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;
using mmPluginManager;
using System.Windows.Forms;

namespace mmPodcasts
{

    [ClassInterface(ClassInterfaceType.AutoDual), ComVisible(true)]
    public class PodcastsPlugin : Plugin
    {

        private SDBApplication SDB;
        private SDBScriptControl script;

        private string _cachePath;
        private string scriptpath;

        private Directory directory;
        private SDBTreeNode node;

        private SDBMenuItem dlMenuItem;
        private SDBSongList downloadSelection;
        private SDBSongList downloads;

        private DownloadHandle downloadHandle;
        private SDBProgress downloadProgress;

        private Timer timer;
        private EventHandler updateHandler;

        #region Plugin Members

        public void init(SDBApplication SDB, SDBScriptControl script)
        {
            try
            {
                // Check version
                if (SDB.VersionHi > 2 || (SDB.VersionHi == 2 && SDB.VersionLo >= 5))
                {
                    this.SDB = SDB;
                    this.script = script;
                    scriptpath = script.ScriptPath;
                    initialiseDirectory();
                    registerUI();

                    // Set up timer
                    timer = new Timer();
                    updateHandler = new EventHandler(updateEvent);
                    timer.Tick += updateHandler;

                    restartTimer();
                }
                else
                    throw new Exception("This version of Media Monkey is not supported by the Podcasts plugin");
            }
            catch (Exception e) {
                displayException(e) ;
            }
        }

        internal void restartTimer()
        {
            timer.Stop();
            timer.Interval = getUpdateFrequency() * 60 * 60 * 1000;
            if (timer.Interval > 0)
                timer.Start();
        }

        private void updateEvent(object o, EventArgs a)
        {
            updateDirectory();
        }

        private int getUpdateFrequency()
        {
            int value = SDB.IniFile.get_IntValue("Podcasts", "UpdateFrequency");
            if (value == 0)
                return 12;
            else
                return value;
        }

        public void dispose()
        {
            // Stop timer
            timer.Stop();
            timer.Tick -= updateHandler;
            timer.Interval = 0;
            timer = null;
        }

        #endregion

        public void save()
        {
            directory.saveCache(_cachePath);
            SDB.IniFile.set_StringValue("Podcasts", "Cache", _cachePath);
        }

        private void initialiseDirectory()
        {
            // Load the cache if it exists
            _cachePath = SDB.IniFile.get_StringValue("Podcasts", "Cache");
            if (_cachePath == null || _cachePath.Equals(""))
            {
                _cachePath = SDB.MyMusicPath + "podcasts\\cache.dat";
                directory = new Directory();

                // Add podcasts specified in ini file
                int count = SDB.IniFile.get_IntValue("Podcasts", "Count");
                for (int i = 1; i <= count; i++)
                {
                    Channel c = new Channel(SDB.IniFile.get_StringValue("Podcasts", "URL" + i));
                    c.title = SDB.IniFile.get_StringValue("Podcasts", "Title" + i);
                    directory.addChannel(c);
                }
            }
            else
            {
                directory = Directory.loadCache(_cachePath);
                updateDirectory();
            }

            // Listen for new channels
            directory.channelAdded += channelAdded;
            directory.channelRemoved += channelRemoved;
            directory.channelUpdated += channelUpdated;
        }

        private void channelAdded(Channel c)
        {
            // Add child
            SDBTreeNode pNode = SDB.MainTree.CreateNode;

            pNode.Caption = c.title;
            pNode.CustomObject = c;
            pNode.IconIndex = 40;
            pNode.OnFillTracksFunct = "handleFillTracks";
            pNode.UseScript = scriptpath;

            SDB.MainTree.AddNode(node, pNode, EnumTreeNodePos.NodePos_AddChildLast);

            SDB.MainTree.SortChildren(node);
        }

        private void channelRemoved(Channel c)
        {
            SDBTreeNode pNode = SDB.MainTree.get_FirstChildNode(node);
            while (pNode != null)
            {
                if (pNode.CustomObject == c)
                {
                    SDB.MainTree.RemoveNode(pNode);
                    return;
                }
                pNode = SDB.MainTree.get_NextSiblingNode(pNode);
            }
        }

        private void channelUpdated(Channel c)
        {
            SDBTreeNode pNode = SDB.MainTree.get_FirstChildNode(node);
            while (pNode != null)
            {
                if (pNode.CustomObject == c)
                {
                    pNode.Caption = c.title;
                    return;
                }
                pNode = SDB.MainTree.get_NextSiblingNode(pNode);
            }
        }

        class UpdateListener
        {
            private Directory.stateListener _delegate;
            private SDBProgress bar;
            private Directory directory;
            private PodcastsPlugin plugin;

            public UpdateListener(SDBApplication SDB, Directory directory, PodcastsPlugin plugin)
            {
                this.directory = directory;
                this.plugin = plugin;
                bar = SDB.Progress;
                bar.Value = 0;
                bar.MaxValue = directory.channels.Count;
                directory.stateUpdated += getDelegate();
                updateText();
            }

            private void updateText()
            {
                bar.Text = "Updating podcast " + bar.Value + "/" + directory.channels.Count + " (" +
                directory.channels[bar.Value].title + ")";
            }

            public void updated(Directory.DirectoryState state)
            {
                bar.Increase();

                if (directory.updateProgress > 0 &&
                    directory.updateProgress <= directory.updateMax)
                {
                    Channel c = directory.channels[directory.updateProgress - 1];
                    plugin.checkEpisodes(c);
                }

                if (state == Directory.DirectoryState.updating)
                    updateText();
                else
                {
                    directory.stateUpdated -= getDelegate();
                    _delegate = null;
                    directory = null;
                    bar = null;
                    GC.Collect();
                }
            }

            public Directory.stateListener getDelegate()
            {
                if (_delegate == null)
                    _delegate = new Directory.stateListener(updated);
                return _delegate;
            }
        }

        /// <summary>
        /// Downloads new episodes automatically and deletes old episodes
        /// </summary>
        /// <param name="channel"></param>
        internal void checkEpisodes(Channel channel)
        {
            SDBSongList newDownloads = SDB.NewSongList;
            SDBSongList deleteTracks = SDB.NewSongList;

            for (int i = 0; i < channel.episodes.Length; i++)
            {
                Episode episode = channel.episodes[i];

                // Find in db
                SDBSongIterator iter = (SDBSongIterator) SDB.Database.QuerySongs(
                    "AND (custom1='" + episode.guid + "')");
                if (iter.EOF)
                {
                    if (i < getAutoDownload())
                        newDownloads.Add(createSong(episode));
                }
                else
                {
                    SDBSongData track = iter.Item;
                    if (track.PlayCounter > 0 && getDeleteListened())
                    {
                        deleteTracks.Add(track);
                    }
                }
            }

            // Update
            downloadPodcasts(newDownloads);
            SDB.Tools.DeleteTracks(deleteTracks, false, EnumYesNo.YesNo_Yes);
        }

        private int getAutoDownload()
        {
            return SDB.IniFile.get_IntValue("Podcasts", "AutoDownload");
        }

        private bool getDeleteListened()
        {
            return SDB.IniFile.get_BoolValue("Podcasts", "DeleteListened");
        }

        private SDBSongData createSong(Episode e)
        {
            SDBSongData track = SDB.NewSongData;
            track.Title = e.title;
            track.Custom1 = e.guid;
            track.AlbumName = e.channel.title;
            track.FileLength = (int)e.fileSize;
            track.Genre = "Podcast";
            track.Comment = e.summary;
            track.IsntInDB = true;
            return track;
        }

        public void updateDirectory()
        {
            if (directory.channels.Count > 0)
            {
                // Register callbacks
                UpdateListener updateListener = new UpdateListener(SDB, directory, this);

                // Start update
                directory.update();

                // Save updates
                if (new System.IO.FileInfo(cachePath).Exists)
                    directory.saveCache(cachePath);
            }
        }

        private void registerUI()
        {
            // Create 'Podcasts' node in tree
            SDBTree tree = SDB.MainTree;
            node = tree.CreateNode;
	        node.Caption = "Podcasts";
	        node.IconIndex = 40;

            // Add to library section
            tree.AddNode(tree.Node_Library, node, EnumTreeNodePos.NodePos_AddChildLast);

            // Add podcasts to node
	        populatePodcasts();

            // Add menu divider
            //SDB.UI.AddMenuItemSep(SDB.UI.Menu_File, 1, 0);

	        // Add menu item to check for new podcasts
	        SDBMenuItem checkMenuItem = SDB.UI.AddMenuItem(SDB.UI.Menu_File, 1, 0);
            checkMenuItem.Caption = "Check for new &podcasts";
            checkMenuItem.IconIndex = 35;
            checkMenuItem.OnClickFunc = "handleUpdateDirectory";
            checkMenuItem.UseScript = scriptpath;

            // Add menus for import/export
            //SDBMenuItem importMI = SDB.UI.AddMenuItem(SDB.UI.Menu_File, 1, 0);
            //importMI.Caption = "&Import podcasts from OPML file";
            //importMI.IconIndex = 35;
            //importMI.OnClickFunc = "handleImport";
            //importMI.UseScript = scriptpath;

            //SDBMenuItem exportMI = SDB.UI.AddMenuItem(SDB.UI.Menu_File, 1, 0);
            //exportMI.Caption = "&Export podcasts to OPML file";
            //exportMI.IconIndex = 35;
            //exportMI.OnClickFunc = "handleExport";
            //exportMI.UseScript = scriptpath;

	        // Add menu item to download selected podcasts
	        dlMenuItem = SDB.UI.AddMenuItem(SDB.UI.Menu_Pop_TrackList, 1, 0);
	        dlMenuItem.Caption = "&Download";
	        dlMenuItem.IconIndex = 35;
	        dlMenuItem.Enabled = false;
            dlMenuItem.UseScript = scriptpath;
            dlMenuItem.OnClickFunc = "handleDownloadPodcasts";
	
	    }

        private void populatePodcasts()
        {
            // Clear list
            node.HasChildren = false;
	        node.HasChildren = true;
	
	        SDBTree tree = SDB.MainTree;
	
	        foreach (Channel podcast in directory.channels) {
		        // Add child
		        SDBTreeNode pNode = tree.CreateNode;
        		
		        pNode.Caption = podcast.title;
                pNode.CustomObject = podcast;
		        pNode.IconIndex = 40;
                pNode.OnFillTracksFunct = "handleFillTracks";
                pNode.UseScript = scriptpath;

		        tree.AddNode(node, pNode, EnumTreeNodePos.NodePos_AddChildLast);
	        }
        	
	        tree.SortChildren(node);
        }

        public void populateChannel()
        {
            Channel channel = (Channel) SDB.MainTree.CurrentNode.CustomObject;
            SDBTracksWindow tracksWindow = SDB.MainTracksWindow;

            foreach (Episode e in channel.episodes) {
                SDBSongData track;
                
                // Check if this track is in DB
                SDBSongIterator iter = (SDBSongIterator) SDB.Database.QuerySongs(
                    "AND (custom1='" + e.guid + "')");
                if (!iter.EOF)
                    track = iter.Item;
                else {
                    track = createSong(e);
                }
                iter = null;
                GC.Collect();

                // Add to track listing
                tracksWindow.AddTrack(track);
            }
            tracksWindow.FinishAdding();
        }

        public void selectionChanged()
        {
            SDBSongList selectedTracks = SDB.SelectedSongList;
	
	        // Check if any of these tracks can be downloaded
	        downloadSelection = filterDownloadableTracks(selectedTracks);
            dlMenuItem.Enabled = downloadSelection.Count > 0;
        }

        private SDBSongList filterDownloadableTracks(SDBSongList selectedTracks)
        {
            SDBSongList filteredTracks = SDB.NewSongList;
            for (int i = 0; i < selectedTracks.Count; i++)
            {
                SDBSongData track = selectedTracks.get_Item(i);
                Episode e = directory.getEpisodeByGuid(track.Custom1);
                if (e != null)
                {
                    if ((track.IsntInDB || track.FileLength < e.fileSize))
                    {
                        filteredTracks.Add(track);
                    }
                }
            }
            return filteredTracks;
        }

        public void downloadPodcasts()
        {
            try
            {
                downloadPodcasts(downloadSelection);
            }
            catch (Exception e)
            {
                displayException(e);
            }
        }

        private void downloadPodcasts(SDBSongList list) {
            // Cancel any broken downloads
            if (downloadProgress != null && downloadProgress.Terminate)
            {
		        cancelDownloads();
	        }

            // Check a download is actually needed
            if (list.Count == 0)
                return;
        	
	        // Add downloads to list
	        if (downloads == null) {
		        downloads = SDB.NewSongList;
	        }
	        for (int i = 0; i < list.Count; i++) {
		        // Don't add if already in list
		        if (!listContains(downloads, list.get_Item(i))) {
			        downloads.Add(list.get_Item(i));
		        }
            }
        	
	        // See if downloads are already in progress
	        if (downloadHandle == null || downloadHandle.state != DownloadHandle.State.downloading) {
		        // Start downloads
                if (downloadProgress == null)
		            downloadProgress = SDB.Progress;
                downloadNextTrack();
	        }
        }

        private void cancelDownloads()
        {
            if (downloadHandle != null && downloadHandle.state != DownloadHandle.State.finished)
            {
                downloadHandle.cancel();
                downloadHandle = null;
            }
            downloadProgress = null;
            downloadHandle = null;
            GC.Collect();
        }

        private bool listContains(SDBSongList songList, SDBSongData song)
        {
            for (int i = 0; i < songList.Count; i++)
            {
                if (songList.get_Item(0).Custom1.Equals(song.Custom1))
                    return true;
            }
            return false;
        }

        private void downloadNextTrack()
        {
            if (downloads.Count == 0)
            {
                cancelDownloads();
                return;
            }
            SDBSongData track = downloads.get_Item(0);
            Episode episode = directory.getEpisodeByGuid(track.Custom1);
        	
            // Start download
            string path = getDownloadPath(episode);
            downloadHandle = episode.download(path);
            track.Path = path;
            track.AlbumName = "Downloading...";
            //track.UpdateDB();
                        
	        // Set up progress bar
            downloadProgress.Value = 0;
            downloadProgress.MaxValue = (int) (episode.fileSize / downloadHandle.chunkSize);
            downloadProgress.Text = "Downloading " + track.Title + "...";

            // Register for events
            downloadHandle.chunkDownloaded += chunkDownloaded;
            downloadHandle.stateChanged += stateChanged;

            downloadHandle.start();
        }

        private string getDownloadPath(Episode episode)
        {
            // Calculate file name
            string filename = clean(episode.title) + ".mp3";

            // Calculate path
	        string path = SDB.IniFile.get_StringValue("Podcasts", "SaveDirectory");
	        if (path == null || path.Equals("")) {
		        path = SDB.MyMusicPath + @"podcasts";
		        SDB.IniFile.set_StringValue("Podcasts", "SaveDirectory", path);
	        }
	        path = path + @"\" + clean(episode.channel.title) + @"\";
	        SDB.Tools.FileSystem.CreateFolder(path);
            return path + filename;
        }

        private string clean(string str)
        {
 	        Regex r = new Regex(@"[\\/\?\*|<>:" + "\"" + "]"); // Illegal file name chars
	        return r.Replace(str, "_");
        }

        private void chunkDownloaded()
        {
            Episode episode = downloadHandle.episode;
            SDBSongData track = downloads.get_Item(0);

        	if (downloadProgress.Terminate) {
                cancelDownloads();
			    return;
		    }
        		
	        // Update progress bar
            downloadProgress.Value = (int) (downloadHandle.downloaded / downloadHandle.chunkSize);
            long dataReceived = downloadHandle.downloaded;
	        downloadProgress.Text = "Downloading " + episode.title + " - " +
		        (dataReceived/1024) + " of " + (episode.fileSize/1024) + " KB (" +
		        downloads.Count + " tracks remaining)...";
        }

        private void stateChanged(DownloadHandle.State state)
        {
            SDBSongData track = downloads.get_Item(0);

            switch (state)
            {
                case DownloadHandle.State.finished:
                    // Delete old track entry from db
                    //SDBSongIterator iter = (SDBSongIterator)SDB.Database.QuerySongs(
                    //    "AND (custom1='" + track.Custom1 + "')");
                    //track = iter.Item;

                    // Update track info
                    track.Title = "";
                    track.AlbumName = "";
                    track.ReadTags();
                    track.Genre = "Podcast";
                    track.UpdateDB();

                    // Remove file from download list
                    downloads.Delete(0);

                    if (downloads.Count > 0)
                        // Start next download
                        downloadNextTrack();
                    else
                        cancelDownloads();
                    break;
                case DownloadHandle.State.error:
                    track.AlbumName = "Download error";
                    track.FileLength = (int) downloadHandle.downloaded;
                    //track.UpdateDB();
                    downloads.Delete(0);
                    //downloads.Add(track);
                    downloadNextTrack();
                    break;
            }
        }

        public void exportOPML()
        {
            FileDialog dialog = new SaveFileDialog();
            dialog.Title = "Export podcasts to OPML";
            dialog.DefaultExt = "opml";
            DialogResult result = dialog.ShowDialog();
            if (result == DialogResult.OK)
            {
                directory.save(dialog.FileName);
            }
        }

        public void importOPML()
        {
            FileDialog dialog = new OpenFileDialog();
            dialog.Title = "Import podcasts from OPML";
            dialog.DefaultExt = "opml";
            dialog.CheckFileExists = true;
            DialogResult result = dialog.ShowDialog();
            if (result == DialogResult.OK)
            {
                try
                {
                    directory.load(dialog.FileName);
                    save();
                }
                catch (Exception e)
                {
                    displayException(e);
                }
            }
        }

        private void displayException(Exception e)
        {
            MessageBox.Show(e.Message + e.StackTrace);
        }

        /*public void trackPlayed()
        {
            SDBSongData song = SDB.Player.CurrentSong;

            // Check if it's a podcast
            if (song.Genre.Equals("Podcast") && !song.Custom1.Equals(""))
            {
                if (song.Path.Equals(""))
                {
                    Episode episode = directory.getEpisodeByGuid(song.Custom1);
                    DownloadHandle handle = episode.download(getDownloadPath(episode));

                    SDBProgress progress = SDB.Progress;
                    progress.MaxValue = (int) (episode.fileSize / handle.chunkSize);
                    handle.chunkDownloaded += delegate()
                    {
                        progress.Value = (int) (handle.downloaded / handle.chunkSize);
                        progress.Text = "Downloading " + episode.title + "...";
                    };
                    handle.stateChanged += delegate(DownloadHandle.State state)
                    {
                        switch (state)
                        {
                            case DownloadHandle.State.finished: 
                            case DownloadHandle.State.error:
                                progress = null;
                                handle = null;
                                GC.Collect();
                                break;
                        }
                    };
                }
            }
        }*/

        internal Directory getDirectory()
        {
            return directory;
        }

        internal string cachePath
        {
            get { return _cachePath; }
            set { _cachePath = value; }
        }
    }
}
